package com.linb.pan.server.common.task;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.linb.pan.schedule.ScheduleTask;
import com.linb.pan.server.common.stream.channel.PanChannels;
import com.linb.pan.server.common.stream.event.log.ErrorLogEvent;
import com.linb.pan.server.modules.file.entity.BPanFileChunk;
import com.linb.pan.server.modules.file.service.IFileChunkService;
import com.linb.pan.storage.engine.core.StorageEngine;
import com.linb.pan.storage.engine.core.context.DeleteFileContext;
import com.linb.pan.stream.core.IStreamProducer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Component
public class CleanExpireChunkFileTask implements ScheduleTask {

    /**
     * 滚动查询的跨度
     */
    private static final Long BATCH_SIZE = 500L;

    @Autowired
    private IFileChunkService fileChunkService;

    @Autowired
    private StorageEngine storageEngine;

    @Autowired
    @Qualifier(value = "defaultStreamProducer")
    private IStreamProducer producer;

    /**
     * 获取定时任务的名称
     */
    @Override
    public String getName() {
        return "CleanExpireChunkFileTask";
    }

    @Override
    public void run() {
        log.info("{} 定时清理分片文件任务启动...", getName());
        List<BPanFileChunk> expireFileChunkRecords; //过期文件列表
        Long scrollPointer = 1L; //指针
        //滚动查询并删除文件和记录
        do {
            //滚动地查询记录
            expireFileChunkRecords = scrollQueryExpireFileChunkRecords(scrollPointer);
            if (CollectionUtils.isNotEmpty(expireFileChunkRecords)) {
                //物理删除
                deleteRealChunkFiles(expireFileChunkRecords);
                //删除记录 b_pan_file_chunk
                List<Long> idList = deleteChunkFileRecords(expireFileChunkRecords);
                //指针变动
                scrollPointer = Collections.max(idList);
            }
        } while (CollectionUtils.isNotEmpty(expireFileChunkRecords));
        log.info("{} 定时清理分片文件任务已完成...", getName());
    }

    /*==================================private==================================*/

    /**
     * 滚动地查询记录
     */
    private List<BPanFileChunk> scrollQueryExpireFileChunkRecords(Long scrollPointer) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.le("expiration_time", new Date());
        queryWrapper.gt("id", scrollPointer); //从1开始，大于1，升序进行
        queryWrapper.last("limit " + BATCH_SIZE); //查询范围是滚动查询的关键
        return fileChunkService.list(queryWrapper);
    }

    /**
     * 物理删除
     */
    private void deleteRealChunkFiles(List<BPanFileChunk> expireFileChunkRecords) {
        DeleteFileContext context = new DeleteFileContext(); //获取上下文
        //提取路径集合
        List<String> realPathList = expireFileChunkRecords.stream().map(BPanFileChunk::getRealPath).collect(Collectors.toList());
        //给上下文设置路径集合
        context.setRealFilePathList(realPathList);
        //物理引擎执行删除
        try {
            storageEngine.delete(context);
        } catch (IOException e) {
            e.printStackTrace();
            saveErrorLog(realPathList);//使用发布事件的方式 保存到错误日志
        }
    }

    /**
     * 删除记录 b_pan_file_chunk
     */
    private List<Long> deleteChunkFileRecords(List<BPanFileChunk> expireFileChunkRecords) {
        //提取id
        List<Long> idList = expireFileChunkRecords.stream().map(BPanFileChunk::getId).collect(Collectors.toList());
        //批量删除
        fileChunkService.removeByIds(idList);
        return idList;
    }

    /**
     * 错误日志处理
     */
    private void saveErrorLog(List<String> realPathList) {
        log.info("[错误日志处理]路径有: {}", JSON.toJSONString(realPathList));
        ErrorLogEvent errorLogEvent = new ErrorLogEvent("删除文件分片记录失败了:" + JSON.toJSONString(realPathList), 0L);
        producer.sendMessage(PanChannels.ERROR_LOG_OUTPUT, errorLogEvent);
    }

}
